package com.jetpack.mvvm.lib.common

import android.app.Application
import android.content.Context
import android.util.Log
import com.alibaba.android.arouter.utils.ClassUtils
import com.jetpack.mvvm.lib.common.inter.InterAppModel
import com.jetpack.mvvm.lib.module.annotation.IModuleInit
import dalvik.system.DexFile
import kotlinx.coroutines.*
import java.lang.reflect.Modifier

object ModuleInit {
    private const val TAG = "ModuleInit"
    private const val GENERATE_PACKAGE = "com.jetpack.mvvm.init"

    fun initModules(context: Application) {
        val pathList = getFileNameByPackageName(context, GENERATE_PACKAGE)
        pathList.filter { it.contains("Init_Module_") }
            .forEach { className ->
                try {
                    val instance = Class.forName(className).getConstructor().newInstance()
                    if (instance is IModuleInit) {
                        instance.cacheModuleClassPath().forEach { moduleClassName ->
                            val module = Class.forName(moduleClassName)
                            try {
                                val obj = module.getConstructor().newInstance()
                                if (obj is InterAppModel) {
                                    obj.initModule(context)
                                }
                            } catch (noMethod: NoSuchMethodException) {
                                val field = module.getDeclaredField("INSTANCE")
                                val modifiers = field.modifiers
                                val flag = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
                                if (flag) {
                                    val fieldValue = field.get(null)
                                    if (fieldValue is InterAppModel) {
                                        fieldValue.initModule(context)
                                    }
                                }
                            }
                        }
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "parse $className error", e)
                }
            }
    }

    private fun getFileNameByPackageName(context: Application, packageName: String): Set<String> =
        runBlocking {
            val classNames: MutableSet<String> = mutableSetOf()
            // 用到 ARouter 的工具类 ClassUtils
            val paths = ClassUtils.getSourcePaths(context)
            val coroutineScope = CoroutineScope(Dispatchers.IO);
            val jobs = mutableListOf<Job>()
            for (path in paths) {
                coroutineScope.launch {
                    //借鉴 ARouter 获取 DexFile 的代码
                    var dexfile: DexFile? = null
                    try {
                        dexfile = if (path.endsWith(".zip")) {
                            //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                            DexFile.loadDex(path, "$path.tmp", 0)
                        } else {
                            DexFile(path)
                        }
                        val dexEntries = dexfile!!.entries()
                        while (dexEntries.hasMoreElements()) {
                            val className = dexEntries.nextElement()
                            if (className.startsWith(packageName)) {
                                classNames.add(className)
                            }
                        }
                    } catch (ignore: Throwable) {
                        Log.e("ARouter", "Scan map file in dex files made error.", ignore)
                    } finally {
                        if (null != dexfile) {
                            try {
                                dexfile.close()
                            } catch (ignore: Throwable) {
                            }
                        }
                    }
                }.apply {
                    jobs.add(this)
                }
            }
            jobs.forEach {
                it.join()
            }
            classNames
        }

    private fun getSourcePaths(context: Context): List<String> {
        context.packageManager.getApplicationInfo(
            context.packageName, 0
        ).apply {
            val sourcePaths: MutableList<String> = mutableListOf()
            sourcePaths.add(sourceDir)
            //instant run
            splitSourceDirs?.let {
                sourcePaths.addAll(it)
            }

            return sourcePaths
        }
    }

}